﻿/**
 * ...
 * @author aboutwhat
 * 
 * 
 * usage:
 * 
 * 
 * 
 *	var options = 
 *  {
 *      mainClass : Args.createOption(["-main"], true),
 *      debug : Args.createFlag(["-debug"]),
 *      target : Args.createOption(["-target"], true, targetValidator, targetCoverter),
 *      lib : Args.createMultiOption(["-lib"]),
 *      exclude : Args.createOption(["-exclude"])
 *  }
 *  
 *   
 *   
 *  var a = new Args(options);
 *  try 
 *  {
 *      a.parse(["-main", "Main", "-lib", "neash", "-lib", "nme", "-debug", "-target", "neko"]);
 *  } 
 *  catch (e : String) 
 *  {
 *      trace("error: " + e);
 *  }
 *   
 *  trace("main class: " + options.mainClass.value);
 *  trace("target: " + options.target.value);
 *  trace("debug flag: " + options.debug.value);
 *  trace("lib: " + options.lib.values);
 *  trace("exclude: " + options.exclude.value);
 */

package bloom.sys;
import bloom.Lib;


private typedef Flag = {
	public var value(getValue, null) : Bool;
}
private typedef Option<T> = {
	public var value(getValue, null) : T;
}

private typedef MultiOption<T> = {
	public var values(default, null) : List<T>;
}


private class Argument<T>
{
    public var flags : Array<String>;
    public var required : Bool;
    
    public var passed : Bool;
    
    function new (flags : Array<String>, ?required = false) 
    {
        this.flags = flags;
        this.required = required;
        this.passed = false;
    }
    
    public function setPassed () : Void { passed = true; }
    
    public var value(getValue, null) : T;
    
    private function getValue () : T { throw "abstract"; return null; }
    public function isFlag () : Bool { throw "abstract"; return null; }
    
	public function addValue (v : String) {throw "abstract";}	
}

private class PFlag extends Argument<Bool>
{
    override public var value(getValue, null) : Bool;
    
    override public function isFlag ():Bool { return true;}
    
    override public function setPassed () : Void 
    { 
        super.setPassed();  
        value = true; 
    }
    
    public function new (flags:Array<String>) 
    {
        super(flags, false);
        value = false;
    }
    
    override private function getValue():Bool
    {
        return value;
    }
}

private class POption<T> extends Argument<T>
{
    override public var value(getValue, null) : T;
    public var values(default, null) : List<T>;
    
    
    private var validator : String -> Bool;
	private var converter : String -> T;
    
    public var isMultiOption : Bool;
    
    override public function isFlag () { return false;}
    
    override private function getValue():T
    {
        return values.first();
    }
    
    
    public function new 
    (
        flags : Array<String>, 
        validator : String->Bool, 
        converter : String->T, 
        ?defaultValue : T, 
        ?required = false 
    ) 
    {
        super(flags, required);
        
        values = new List();
		isMultiOption = false;
        
        if (defaultValue != null) values.push(defaultValue);
		
        this.value = defaultValue;
		this.converter = converter;
		this.validator = validator;
	}
    
    
    override public function addValue (v : String) 
    {
		
		if (v == null) throw "No Value specified for Option " + flags;

		if (validator(v)) 
        {
            // single Options can only have one value
            if (!isMultiOption) values.clear();
            
            values.add(converter(v));
		} 
        else 
        {
			throw "Value : " + v + " for Option " + flags + " is not valid";
		}
	}
}


class Args<T : Dynamic<Argument<Dynamic>>>
{
	
    // -------------- factory methods ------------------
    
    public static function createOption <T>
    ( 
        flags : Array<String>, 
        ?required : Bool = false,
        ?validator : String -> Bool, 
        ?converter : String -> T, 
        ?defaultValue:T
        
    ) 
    : Option<T> 
    {

        if (validator == null) validator = Lib.isTrue;
        if (converter == null) converter = cast Lib.identity;
        
		return new POption<T>(flags, validator, converter, defaultValue, required);
	}
    
    public static function createMultiOption <T>
    ( 
      flags : Array<String>, 
      ?validator : String -> Bool, 
      ?converter : String -> T
    ) 
    : MultiOption<T> 
    {
		var o : POption<T> = cast createOption(flags, validator, converter);
        o.isMultiOption = true;
		return o;

	}
    
	
	public static function createFlag( flags : Array<String> ) : Flag 
    {
		var o = new PFlag(flags);
		return o;
	}
    
    // -------------- class members ------------------
    
	public var results : T;
    
    
	public var optHash : Hash<Argument<Dynamic>>;
	
	public function new ( struct:T )
	{
		results = struct;
		optHash = new Hash();
        
		for (i in Reflect.fields(results)) 
        {
			var opt : Argument<Dynamic> = cast Reflect.field(results, i);
            
			for (f in opt.flags)
				optHash.set(f, opt);
		}
		
	}
	
	public function parse ( args : Array<String> ) 
    {
		while (args.length > 0) 
        {
			if (optHash.exists(args[0])) 
            {
				var opt : Argument<Dynamic> = optHash.get(args[0]);
                
				if (Lambda.has(opt.flags, args.shift())) 
                {
					opt.setPassed();
                    
					if (!opt.isFlag()) 
                        opt.addValue(args.shift());
				}
			} 
            else 
            {
				throw "Error option " + args[0] + " is not valid";
			}
		}
		
		for (i in optHash.keys()) 
        {
			var f = optHash.get(i);
			
            // option is required, but was not passed
			if (f.required && !f.passed) 
                throw "required option " + f.flags + " was not passed";
		}
	}
}


